package semantico;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

import java.util.HashMap;


import java.util.Stack;

@SuppressWarnings("unchecked")
public class SemanticoCIL implements ISemantico {

	HashMap variaveis = new HashMap();
	HashMap nomeClasse = new HashMap();
	HashMap objetos = new HashMap();
	HashMap metodos = new HashMap();
	HashMap atributo = new HashMap();
	Stack pilhaSe = new Stack();
	int contaSe = 0;
	String nomePrograma,classe;
	StringBuffer sb = new StringBuffer();

	public void escreverArquivo() {
		try {
			System.out.print("Codigo intermediario...");
			BufferedWriter out = new BufferedWriter(new FileWriter(nomePrograma
					+ ".il"));
			out.write(sb.toString());
			out.close();
			System.out.println("OK");
		} catch (IOException e) {
			System.err.println("Erro ao tentar salvar em arquivo:"
					+ e.getMessage());
		}
	}

	public void compilaIL() {
		try {
			System.out.print("Compilacao codigo IL...");
			Runtime.getRuntime().exec("C:\\WINDOWS\\Microsoft.NET\\Framework\\v2.0.50727\\ilasm.exe "
							+ nomePrograma+ ".il");
			System.out.println("OK");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	//inicio do programa
	public void programaIni(String nomePrograma) {
		System.out.print("Parsing...");
		this.nomePrograma = nomePrograma;
		sb.append(".assembly " + nomePrograma + "{}\n");
	}

	//corpo main do programa
	public void corpoMain() {
		sb.append(".class " + nomePrograma);
		sb.append("{\n");
		sb.append("	.method static public hidebysig default void Main() cil managed\n");
		sb.append("	{\n");
		sb.append("		.entrypoint\n");
	}

	//fim do programa
	public void programaFim() {
		sb.append("		ret\n");
		sb.append("	}\n");
		sb.append("}\n");
	}

	//fim da analise (ou seja, fim do assembly, realiza��o da grava��o em arquivo
	//e compila��o para um .exe)
	public void fimAnalise() {
		System.out.println("OK");
		escreverArquivo();
		compilaIL();
	}

	// est� guardando a classe em um HashMap e seu valor � o tipo
	public void classeIni(String nomeClasse, String tipo) {
		this.classe=nomeClasse;
		if(tipo.equals("publico")){
			this.nomeClasse.put(nomeClasse, tipo);
			sb.append(".class public auto ansi beforefieldinit " + nomeClasse+ " extends [mscorlib]System.Object\n");
			sb.append("{\n");
			System.out.println("Entrei no inicio de uma classe publica");			
		}else if(tipo.equals("privado")){
			this.nomeClasse.put(nomeClasse, tipo);
			sb.append(".class private auto ansi beforefieldinit " + nomeClasse+ " extends [mscorlib]System.Object \n");
			sb.append("{\n");
			System.out.println("Entrei no inicio de uma classe privada");
		}else{
			System.err.println("erro: Visibilidade inv�lida");
			System.exit(1);
		}
		
		/*if (this.nomeClasse.containsKey(nomeClasse)) {
			String tipoClasse = (String) this.nomeClasse.get(nomeClasse);
			if (tipo.equals("publico")) {
				//System.out.println(tipoClasse);
				if (tipoClasse.equals(tipo)) {
					sb.append(".class public " + nomeClasse+ " extends [mscorlib]System.Object\n");
					sb.append("{\n");
					System.out.println("Entrei no inicio de uma classe publica");
				} else {
					System.err.println("erro:Tipo diferente da assinatura da classe");
					System.exit(1);
				}
			} else if (tipo.equals("privado")) {
				if(tipoClasse.equals(tipo)){
					sb.append(".class private " + nomeClasse+ " extends [mscorlib]System.Object \n");
					sb.append("{\n");
					System.out.println("Entrei no inicio de uma classe privada");
				}else{
					System.err.println("erro:Tipo diferente da assinatura da classe");
					System.exit(1);
				}
			} else {
				System.err.println("erro: Tipo de classe indefinido");
				System.exit(1);
			}
		}else{
			System.err.println("erro: Classe indefinida na assinatura");
			System.exit(1);
		}*/
	}

	public void classeFim() {
		sb.append("}\n");
		System.out.println("Fim da classe");
	}

	// construtor padrao da classe
	public void construtorPadrao(String nomeClasse) {
		sb.append("	.method public instance default void .ctor() il managed\n");
		sb.append("	{\n");
		sb.append("		ldarg.0\n");
		sb.append("		call instance void [mscorlib]System.Object::.ctor()\n");
		sb.append("		ret\n");
		sb.append("	}\n");
	}

	public void declaraAtributo(String str, String tipo) {
		this.atributo.put(str, this.classe);//ver depois
		if (tipo.equals("visivel")) {
			variaveis.put(str, new Integer(99));
			sb.append("		.field public int32 " + str + " \n");
			System.out.println("Encontrei um atributo publico");
		} else if (tipo.equals("escondido")) {
			variaveis.put(str, new Integer(99));
			sb.append("		.field private int32 " + str + " \n");
			System.out.println("Encontrei um atributo privado");
		} else {
			System.err.println("erro:Visibilidade inv�lida");
			System.exit(1);
		}
	}

	//Metodo sem passagem de parametro
	public void inicioMetodo(String str, String tipo, String retorno) {
		if (tipo.equals("publico")) {
			if (retorno.equals("integer")) {
				sb.append("	.method public hidebysig instance default int32 " + str
						+ " () il managed\n");
				sb.append("	{\n");
				sb.append("		ldarg.0\n");
				System.out.println("Encontrei um metodo com retorno e publico");
			} else if (retorno.equals("vazio")) {
				sb.append("	.method public hidebysig instance default void " + str
						+ " () il managed\n");
				sb.append("	{\n");
				sb.append("		ldarg.0\n");
				System.out.println("Encontrei um m�todo sem retorno e publico");
			} else {
				System.err.println("erro: Tipo de retorno inv�lido");
				System.exit(1);
			}
		} else if (tipo.equals("escondido")) {
			if (retorno.equals("integer")) {
				sb.append("	.method private hidebysig instance default int32 " + str
						+ " () il managed\n");
				sb.append("	{\n");
				sb.append("		ldarg.0\n");
				System.out.println("Encontrei um metodo com retorno e privado");
			} else if (retorno.equals("voidi")) {
				sb.append("	.method private hidebysig instance default void " + str
						+ " () il managed\n");
				sb.append("	{\n");
				sb.append("		ldarg.0\n");
				System.out.println("Encontrei um m�todo sem retorno e privado");
			} else {
				System.err.println("erro: Tipo de retorno inv�lido");
				System.exit(1);
			}
		} else {
			System.err.println("erro: Visibilidade inv�lida");
			System.exit(1);
		}
	}

	public void fimMetodo() {
		sb.append("		ret\n");
		sb.append("	}\n");
	}
	
	//para os m�todos com retorno
	public void retornoMetodo(String nomeAtributo) {
		if(this.atributo.containsKey(nomeAtributo)){
			sb.append("		ldfld int32 " + this.atributo.get(nomeAtributo) + "::" + nomeAtributo + "\n");
		}else{
			System.err.println("erro:Atributo n�o declarado");
			System.exit(1);
		}
		
	}

	//chamada de um metodo que tem retorno
	public void chamaMetodoGet(String nomeObjeto, String nomeMetodo){
		if(objetos.containsKey(nomeObjeto)){
			sb.append("		ldloc "+nomeObjeto+"\n");
			sb.append("		callvirt instance int32 "+objetos.get(nomeObjeto)+"::"+nomeMetodo+"()\n");
			sb.append("		call void class [mscorlib]System.Console::WriteLine(int32)\n");
			System.out.println("Mostrei um atributo de um objeto =P ");
		}else{
			System.err.println("erro:Objeto nao declarado");
		}
		
	}
	
	//chamada de um metodo comum, sem parametro nem retorno
	public void chamaMetodo(String nomeObjeto, String nomeMetodo){
		if(objetos.containsKey(nomeObjeto)){
			sb.append("		ldloc "+nomeObjeto+"\n");
			sb.append("		callvirt instance void "+objetos.get(nomeObjeto)+"::"+nomeMetodo+"()\n");
			System.out.println("Fiz um metodo de atribui��o dentro da classe xDxDxD ");
		}else{
			System.err.println("erro:Objeto nao declarado");
		}
		
	}
	
	//declara uma variavel
	//declara uma variavel no programa
	public void declaraVariavel(String str) {
		variaveis.put(str, new Integer(99));
		sb.append("		.locals(int32 " + str + ")\n");
		System.out.println("Encontrei uma variavel local");
	}

	//decla um objeto, fazendo uma valida��o para saber se 
	//a classe do mesmo existe antes de ser instanciado
	public void declaraObjeto(String str, String classe) {
		if (this.nomeClasse.containsKey(classe)) {
			variaveis.put(str, null);
			objetos.put(str, classe);
			sb.append("		.locals init(object " + str + ")\n");
			System.out.println("Encontrei um objeto");
		} else {
			System.err.println("Classe n�o declarada");
			System.exit(1);
		}

	}

	//somente ap�s ter sido declarado � que o objeto pode ser
	//instanciado com o newobj
	public void instanciacao(String str, String nomeClasse) {
		if (variaveis.containsKey(str)) {
			if(nomeClasse.equals(objetos.get(str))){
				variaveis.put(str, nomeClasse);
				sb.append("		newobj instance void "+nomeClasse+"::.ctor()\n");
				sb.append("		stloc "+str+"\n");
				System.out.println("Objeto instanciado!!");
			}else{
				System.err.println("erro: Classe n�o se refere ao objeto");
				System.exit(1);
			}
		} else {
			System.err.println("erro: Objeto " + str + " n�o declarado.");
			System.exit(1);
		}
	}
	
	//atribuir um valor � um objeto ou uma variavel
	public void atribuicao(String str, Object valor) {
		if (atributo.containsKey(str)){
			variaveis.put(str, valor);
			sb.append("		stfld int32 " +this.atributo.get(str)+"::"+ str + "\n");
		}else if (variaveis.containsKey(str)) {
			variaveis.put(str, valor);
			sb.append("		stloc " + str + "\n");
		} else {
			System.err.println("erro: Variavel " + str + " nao declarada.");
			System.exit(1);
		}
	}

	//imprime valores de uma variavel em questao
	public void impressaoVariavel(String str) {
		if (variaveis.containsKey(str)) {
			sb.append("		ldloc " + str + "\n");
			sb
					.append("		call void [mscorlib]System.Console::WriteLine(int32)\n");
		} else {
			System.err.println("erro: Variavel " + str + " nao declarada.");
			System.exit(1);
		}

	}

	//imprime um valor digitado e inteiro
	public void impressaoValor() {
		sb.append("		call void [mscorlib]System.Console::WriteLine(int32)\n");
	}

	//inicio do se
	public void condicaoInicio(Boolean b) {
		contaSe++;
		pilhaSe.push(new Integer(contaSe));

		sb.append("		ldc.i4 " + (b ? 1 : 0) + "\n");
		sb.append("		brfalse senao" + contaSe + "\n");
	}

	//entao
	public void condicaoEntao() {
		sb.append("		br fimif" + pilhaSe.peek() + "\n");
	}

	//senao
	public void condicaoSenao() {
		sb.append("    senao" + pilhaSe.peek() + ":\n");
	}

	//para sair do comando se
	public void condicaoFim() {
		sb.append("		fimif" + pilhaSe.pop() + ":\n");
	}

	//carrega um numero inteiro na pilha
	public void numero(Integer num) {
		sb.append("		ldc.i4 " + num + "\n");
	}

	//opera��o de soma
	public void somar() {
		sb.append("		add\n");
	}

	//opera��o de subtra��o
	public void subtrair() {
		sb.append("		sub\n");
	}

	//opera��o de divis�o
	public void dividir() {
		sb.append("		div\n");
	}

	//opera��o de multiplica��o
	public void multiplicar() {
		sb.append("		mul\n");
	}
}
